/***********************************************************
 * Product Name: ExcelQuicker
 * Copyright (c)2005, Eunge
 * Author: Eunge
 * Web Site: www.eudev.net
 * Mail/MSN: eudev.net@yeah.net
 * ********************************************************/
using System;
using System.Text;
using System.IO;
using System.Reflection;
using System.Collections;

namespace ExcelQuicker.Framework
{
	/// <summary>
	/// Application of ExcelQuicker, derived from MarshalByRefObject. Supporting remote object visiting
	/// </summary>
	public class EQApplication:EQObject, IDisposable
	{
		private string mExcelFileName = "";
		private string mTemplateFileName = "";
		private EQBool mIsSingleTemplateWorksheet = EQBool.Default;
		private ArrayList mWorksheets = new ArrayList();
		private Excel.Workbook mWorkbook;
		private Excel.Application mApplication;
		private EQEventWorkbookHandler mBeforeOperationEvent;
		private EQEventWorkbookHandler mAfterOperationEvent;
		private Hashtable mOutputFileList = new Hashtable();

		/// <summary>
		/// Set/Get the workbook handler will be executed before it executes by ExcelQuicker
		/// </summary>
		public EQEventWorkbookHandler BeforeOperationEvent
		{
			get
			{
				return this.mBeforeOperationEvent;
			}
			set
			{
				this.mBeforeOperationEvent = value;
			}
		}


		/// <summary>
		/// Set/Get the workbook handler will be executed after it was executed by ExcelQuicker
		/// </summary>
		public EQEventWorkbookHandler AfterOperationEvent
		{
			get
			{
				return this.mAfterOperationEvent;
			}
			set
			{
				this.mAfterOperationEvent = value;
			}
		}


		/// <summary>
		/// Get template file name
		/// </summary>
		public string TemplateFileName
		{
			get
			{
				return this.mTemplateFileName;
			}
		}


		/// <summary>
		/// Get amount of worksheets
		/// </summary>
		public int WorksheetCount
		{
			get
			{
				return this.Worksheets.Count;
			}
		}


		/// <summary>
		/// Get whether using template mode
		/// </summary>
		public bool TemplateMode
		{
			get
			{
				return (this.mTemplateFileName.Length==0)?false:true;
			}
		}


		/// <summary>
		/// Get whether using single worksheet mode
		/// </summary>
		public bool IsSingleTemplateWorksheet
		{
			get
			{
				return (this.mIsSingleTemplateWorksheet==EQBool.True)?true:false;
			}
		}

		/// <summary>
		/// Get worksheets array list
		/// </summary>
		private ArrayList Worksheets
		{
			get
			{
				return this.mWorksheets;
			}
		}


		/// <summary>
		/// <br>Add a new EQWorksheet object to the current EQApplication object.</br>
		/// </summary>
		/// <param name="objEQWorksheet"></param>
		/// <example>
		/// EQApplication objApp = new EQApplication(strFileName);
		/// EQWorksheet objWs = new (strWorksheetName);
		/// objApp.Add(objWs);	//If you want to output excel with information including in the objWs, you must add it to objApp
		/// </example>
		public void Add(EQWorksheet objEQWorksheet)
		{
			objEQWorksheet.TemplateMode = this.TemplateMode;
			this.Worksheets.Add(objEQWorksheet);
		}


		/// <summary>
		/// Finilize resource, decrease reference and clear memory
		/// </summary>
		public void Dispose()
		{
			if(this.mWorkbook != null)
			{
				this.mWorkbook.Close(false, null, null);
				System.Runtime.InteropServices.Marshal.ReleaseComObject(this.mWorkbook);
				this.mWorkbook = null;
			}

			if(this.mApplication != null)
			{
				this.mApplication.Quit();
				System.Runtime.InteropServices.Marshal.ReleaseComObject(this.mApplication);
				this.mApplication  = null;
			}

			GC.Collect();
		}


		/// <summary>
		/// Constructor without any parameter will output excel report under the application bin folder
		/// </summary>
		public EQApplication()
		{
			string strOutputFileName = string.Empty;

			if(EQConfiguration.OutputFilePath != string.Empty)
				strOutputFileName = EQWebKit.GenerateAutoFullFileName(EQConfiguration.OutputFilePath);
			else
				strOutputFileName = EQWebKit.GenerateAutoFullFileName(System.Web.HttpRuntime.BinDirectory);

			strOutputFileName = EQKit.ChangeDuplicateFileName(strOutputFileName);
			this.AddOutputFile(strOutputFileName, EQReportFormat.Excel);

			this.mExcelFileName = EQKit.ChangeSubfix(strOutputFileName, ".xls");
		}


		/// <summary>
		/// Construct object will output excel file as outputFileName. If extension file name of outputFileName is not .xls, ExcelQuicker will change it to .xls and return correct file name when call Output() function finally. 
		/// </summary>
		/// <param name="outputFileName">
		/// <br>Unique physical full output file name</br>
		/// </param>
		/// <example>EQApplication objApp = new EQApplication("c:\projects\projectname\exceltempreports\20123211221.xls")</example>
		public EQApplication(string outputFileName):this(outputFileName, EQReportFormat.Excel)
		{
		}


		/// <summary>
		/// Construct object will output report file as outputFileName based on param format. ExcelQuicker will check whether the extension name is relative to param format, if it is, ExcelQuicker will change it to the correct extension name.
		/// </summary>
		/// <param name="outputFileName">
		/// <br>Unique physical full output file name</br>
		/// </param>
		/// <param name="format"></param>
		/// <example>EQApplication objApp = new EQApplication("c:\projects\projectname\exceltempreports\20123211221.xls", EQReportFormat.HTML);</example>
		public EQApplication(string outputFileName, EQReportFormat format)
		{
			string strOutputFileName = EQKit.GetFullFileName(outputFileName);
			strOutputFileName = EQKit.ConvertFileName(strOutputFileName, format);
			strOutputFileName = EQKit.ChangeDuplicateFileName(strOutputFileName);
			this.AddOutputFile(strOutputFileName, format);
			this.mExcelFileName = EQKit.ChangeSubfix(strOutputFileName, ".xls");
		}


		/// <summary>
		/// Constructor, using template excel file. It's more efficient and convenient when using template excel file to develop report
		/// </summary>
		/// <param name="outputFileName">Unique physical full output file name</param>
		/// <param name="templateFileName">Physical full template file name</param>
		/// <param name="isSingleTemplateWorksheet">Whether use single template mode</param>
		public EQApplication(string outputFileName, string templateFileName, bool isSingleTemplateWorksheet):this(outputFileName, EQReportFormat.Excel, templateFileName, isSingleTemplateWorksheet)
		{
		}


		/// <summary>
		/// Constructor, using template excel file. It's more efficient and convenient when using template excel file to develop report
		/// </summary>
		/// <param name="outputFileName">Unique physical full output file name</param>
		/// <param name="format">Format of report file</param>
		/// <param name="templateFileName">Physical full template file name</param>
		/// <param name="isSingleTemplateWorksheet">Whether use single template mode</param>
		public EQApplication(string outputFileName, EQReportFormat format, string templateFileName, bool isSingleTemplateWorksheet):this(outputFileName, format)
		{
			if(Path.GetDirectoryName(templateFileName) == "")
				this.mTemplateFileName = EQConfiguration.TemplateFilePath + templateFileName;
			else
				this.mTemplateFileName = templateFileName;
			if(!File.Exists(this.mTemplateFileName))
				throw new EQException("EQ1039", this.mTemplateFileName);
			this.mIsSingleTemplateWorksheet = (isSingleTemplateWorksheet)?EQBool.True:EQBool.False;
		}


		/// <summary>
		/// Add a new output file. ExcelQuicker will output this file(fileName) in the format at runtime. If the extension of file name is not suitable for the report format, ExcelQuicker will change it to suitable one automatically.
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="format"></param>
		public void AddOutputFile(string fileName, EQReportFormat format)
		{
			if(this.mOutputFileList.ContainsKey(format))
				throw new EQException("EQ1036", format.ToString());
			string strFileName = fileName;
			strFileName = EQKit.GetFullFileName(strFileName);
			strFileName = EQKit.ConvertFileName(strFileName, format);
			this.mOutputFileList.Add(format, strFileName);
		}

		/// <summary>
		/// Begin to call Excel Com Interface to generate report files.
		/// </summary>
		/// <remarks>Return the output file list(Hashtable)</remarks>
		public Hashtable Output()
		{
			try
			{
				this.mApplication = new Excel.ApplicationClass();	//Construct Excel.Application object.
				this.mApplication.DisplayInfoWindow = false;
				this.mApplication.DisplayAlerts = false;
				this.GeneratedWorkbook();	//Finish initialization of excel document, and get this.ExcelWorkbook object.
				this.mWorkbook.RunAutoMacros(Excel.XlRunAutoMacro.xlAutoOpen);

				if(this.mBeforeOperationEvent != null)
					this.mBeforeOperationEvent(this.mWorkbook);

				for(int index=0; index<this.WorksheetCount; index++)	//Output each worksheet one by one.
				{
					Excel.Worksheet objWorksheet = (Excel.Worksheet)this.mWorkbook.Worksheets[index+1];
                    EQWorksheet eqWorksheet = (this.Worksheets[index] as EQWorksheet);
                    if(true == eqWorksheet.IsOutput)
                    {
                        eqWorksheet.Output(objWorksheet);
                    }
				}

				if(this.mAfterOperationEvent != null)
					this.mAfterOperationEvent(this.mWorkbook);

				this.SaveReport(this.mWorkbook);
			}
			finally
			{
				this.Dispose();
			}

			//If convert HTML file failed, then keep it as original.
			try
			{
				if(this.mOutputFileList.ContainsKey(EQReportFormat.HTML))
					EQWebKit.ConvertHTMLReport(this.mOutputFileList[EQReportFormat.HTML].ToString());
			}
			catch{}

			return this.mOutputFileList;
		}


		/// <summary>
		/// Return output file name with sepcialized format
		/// </summary>
		/// <param name="format"></param>
		/// <returns></returns>
		public string GetOutputFileName(EQReportFormat format)
		{
			if(!this.mOutputFileList.ContainsKey(format))
				throw new EQException("EQ1037", format.ToString());
			return this.mOutputFileList[format] as string;
		}


		/// <summary>
		/// Save the report file saved in hash table this.mOutputFileList
		/// </summary>
		/// <param name="objWorkbook"></param>
		protected void SaveReport(Excel.Workbook objWorkbook)
		{
			if(this.mOutputFileList.ContainsKey(EQReportFormat.Excel))
			{
				objWorkbook.Save();
				this.mOutputFileList[EQReportFormat.Excel] = this.mExcelFileName;
			}

			if(this.mOutputFileList.ContainsKey(EQReportFormat.HTML))
			{
				objWorkbook.SaveAs(this.mOutputFileList[EQReportFormat.HTML].ToString(), Excel.XlFileFormat.xlHtml, this.Missing, this.Missing, false, false, Excel.XlSaveAsAccessMode.xlNoChange, this.Missing, this.Missing, this.Missing, this.Missing);
			}

			/****************************************************************************************************************************************************
			  Description:
			  Only the office2003 can support OWC. ExcelQuicker uses Excel2000 COM. Therefore, I comment these codes. 
			  If you wana these function, you can include Excel2003's COM instead of COMs included by ExcelQuicker.
			  And cancel these comments.
			 ***************************************************************************************************************************************************/
//			if(this.mOutputFileList.ContainsKey(EQReportFormat.Spreadsheet))
//			{
//				Excel.PublishObject objPublishObject = objWorkbook.PublishObjects.Add(Excel.XlSourceType.xlSourceSheet, this.mOutputFileList[EQReportFormat.Spreadsheet].ToString(), this.Missing, this.Missing, this.Missing, this.Missing, this.Missing);
//				objPublishObject.HtmlType = Excel.XlHtmlType.xlHtmlCalc;
//				objPublishObject.Publish(true);
//			}
		}


		/// <summary>
		/// Generates worksheets up to the amount of EQWorksheet objects. And initializes Excel.Workbook hanlder.
		/// </summary>
		private void GeneratedWorkbook()
		{
			if(this.TemplateMode)
			{
				File.Copy(this.TemplateFileName, this.mExcelFileName, true);	//Copy template file to destination
				if(this.IsSingleTemplateWorksheet)	//If only one template worksheet
				{
					this.GeneratedWorksheet(this.mExcelFileName);
				}
				else
				{
					this.mWorkbook = this.mApplication.Workbooks.Open(this.mExcelFileName, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing);
					if(this.mWorkbook.Worksheets.Count != this.WorksheetCount)
						throw new EQException("EQ1005", this.mWorkbook.Worksheets.Count.ToString(EQKit.ObjNumberFormatInfo), this.WorksheetCount.ToString(EQKit.ObjNumberFormatInfo));	//The count of template worksheets {0} is different from EQWorksheets {1}.
				}
			}
			
			else
			{
				this.GeneratedExcelFile();	//Create excel file
				this.GeneratedWorksheet(this.mExcelFileName);	//Create several worksheet
			}
		}


		/// <summary>
		/// Duplicate N-1 worksheets in the template excel name. If the worksheet embedded is not unique, throw exception
		/// </summary>
		/// <param name="fileName"></param>
		private void GeneratedWorksheet(string fileName)
		{
			this.mWorkbook = this.mApplication.Workbooks.Open(fileName, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing, this.Missing);

			if(this.mWorkbook.Worksheets.Count != 1)	//If the amount of worksheets in the excel file is not unique, throw exception
				throw new EQException("EQ1006", new string[]{fileName});

			Excel.Worksheet objWorksheet = (Excel.Worksheet)this.mWorkbook.Worksheets.get_Item(1);
			for(int i=0; i<this.WorksheetCount-1; i++)	//Duplicate n-1 worksheets into the destined file
			{
				objWorksheet.Activate();
				objWorksheet.Copy(objWorksheet, this.Missing);
			}
		}


		/// <summary>
		/// Copy the excel template file in the current assembly to the destination
		/// </summary>
		private void GeneratedExcelFile()
		{
			Stream objStream = null;
			FileStream objFileStream = null;
			try
			{
				Assembly objAssembly = Assembly.GetExecutingAssembly();
				string[] strArrayAssembly = objAssembly.GetManifestResourceNames();
				objStream = objAssembly.GetManifestResourceStream(strArrayAssembly[0]);

				if(File.Exists(this.mExcelFileName))	//If the file exists, delete it
					File.Delete(this.mExcelFileName);

				objFileStream = new FileStream(this.mExcelFileName, FileMode.CreateNew);
				byte[] data = new byte[5120];
				int readCount = 0;
				while(true)
				{
					readCount = objStream.Read(data,0,5120);
					if(readCount <= 0)
						break;
					else if(readCount != 5120)
						objFileStream.Write(data,0,readCount);
					else
						objFileStream.Write(data,0,5120);
				}
			}
			finally
			{
				objStream.Close();
				objFileStream.Close();
			}
		}
	}
}
